﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace ImageProcessing
{
	public static class Enhance
	{

		public static double[, ,] Linear(double[, ,] adImage, int nWidth, int nHeight, int nLower, int nUpper, out double[] adData, double[] adArgs)
		{
			double[, ,] adOut = new double[nWidth, nHeight, 3];
			double min, max;
			min = MinMax(adImage, nWidth, nHeight, false);
			max = MinMax(adImage, nWidth, nHeight, true);
			#region set Data Array
			adData = new double[256];
			for (int i = 0; i < 256; i++)
			{
				if (i < nLower)
				{
					adData[i] = nLower / 255.0;
				} //end if
				else if (i <= nUpper)
				{
					adData[i] = i / 255.0;
				} //end if
				else
					adData[i] = nUpper / 255.0;
			} //end for
			#endregion set Data Array

			for (int j = 0; j < nHeight; j++)
			{
				for (int i = 0; i < nWidth; i++)
				{
					if (adArgs[1] < 1.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adData[(int)(adImage[i, j, 1] * 255)];
						adOut[i, j, 2] = adData[(int)(adImage[i, j, 2] * 255)];
					}//end if
					else if (adArgs[1] < 2.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					} //end else
					else
					{//HSI
						adOut[i, j, 0] = adData[(int)(((adImage[i, j, 0] - min) / (max - min)) * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					}
				} //end for
			} //end for
			return adOut;
		}

		public static double[, ,] Bias(double[, ,] adImage, int nWidth, int nHeight, int nLower, int nUpper, out double[] adData, double[] adArgs)
		{
			double[, ,] adOut = new double[nWidth, nHeight, 3];
			double min, max;
			min = MinMax(adImage, nWidth, nHeight, false);
			max = MinMax(adImage, nWidth, nHeight, true);
			//invalid parameter will result in default value
			if (adArgs[0] > 1 || adArgs[0] < 0)
			{
				adArgs[0] = .2;
			} //end if
			#region set Data Array
			adData = new double[256];
			for (int i = 0; i < 256; i++)
			{
				if (i < nLower)
				{
					adData[i] = nLower / 255.0;
				} //end if
				else if (i <= nUpper)
				{
					adData[i] = Math.Pow(i / 255.0, Math.Log10(adArgs[0]) / Math.Log10(0.5));
				} //end if
				else
					adData[i] = nUpper / 255.0;
			} //end for
			#endregion set Data Array

			for (int j = 0; j < nHeight; j++)
			{
				for (int i = 0; i < nWidth; i++)
				{
					if (adArgs[1] < 1.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adData[(int)(adImage[i, j, 1] * 255)];
						adOut[i, j, 2] = adData[(int)(adImage[i, j, 2] * 255)];
					}//end if
					else if (adArgs[1] < 2.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					} //end else
					else
					{//HSI
						adOut[i, j, 0] = adData[(int)(((adImage[i, j, 0]-min)/(max-min)) * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					}
				} //end for
			} //end for

			return adOut;
		}//end Bias

		public static double[, ,] Gain(double[, ,] adImage, int nWidth, int nHeight, int nLower, int nUpper, out double[] adData, double[] adArgs)
		{
			double[, ,] adOut = new double[nWidth, nHeight, 3];
			double x;
			double min, max;
			min = MinMax(adImage, nWidth, nHeight, false);
			max = MinMax(adImage, nWidth, nHeight, true);
			//invalid parameter will result in default value
			if (adArgs[0] > 1 || adArgs[0] < 0)
			{
				adArgs[0] = .2;
			} //end if
			#region set Data Array
			adData = new double[256];
			for (int i = 0; i < 256; i++)
			{
				if (i < nLower)
				{
					adData[i] = nLower / 255.0;
				} //end if
				else if (i <= nUpper)
				{
					x = i / 255.0;
					//for first half curve one way, then go the other
					if (x < .5)
					{
						adData[i] = Math.Pow(2 * x, Math.Log10(1 - adArgs[0]) / Math.Log10(.5)) / 2;
					} //end if
					else
					{
						adData[i] = 1 - Math.Pow(2 - 2 * x, Math.Log10(1 - adArgs[0]) / Math.Log10(.5)) / 2;
					} //end else
					//	adData[i] = Math.Pow(i / 255.0, Math.Log10(adArgs[0]) / Math.Log10(0.5));
				} //end if
				else
					adData[i] = nUpper / 255.0;
			} //end for
			#endregion set Data Array

			for (int j = 0; j < nHeight; j++)
			{
				for (int i = 0; i < nWidth; i++)
				{
					if (adArgs[1] < 1.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adData[(int)(adImage[i, j, 1] * 255)];
						adOut[i, j, 2] = adData[(int)(adImage[i, j, 2] * 255)];
					}//end if
					else if (adArgs[1] < 2.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					} //end else
					else
					{//HSI
						adOut[i, j, 0] = adData[(int)(((adImage[i, j, 0] - min) / (max - min)) * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					}
				} //end for
			} //end for

			return adOut;
		}//end Gain

		public static double[, ,] Exp(double[, ,] adImage, int nWidth, int nHeight, int nLower, int nUpper, out double[] adData, double[] adArgs)
		{
			double power = adArgs[0];
			double[, ,] adOut = new double[nWidth, nHeight, 3];
			double x;
			double min, max;
			min = MinMax(adImage, nWidth, nHeight, false);
			max = MinMax(adImage, nWidth, nHeight, true);

			#region set Data Array
			adData = new double[256];
			for (int i = 0; i < 256; i++)
			{
				if (i < nLower)
				{
					adData[i] = nLower / 255.0;
				} //end if
				else if (i <= nUpper)
				{
					x = i / 255.0;
					double t = (i - nLower) / (double)(nUpper - nLower);
					adData[i] = Math.Pow(t, power);
					//	adData[i] = Math.Pow(i / 255.0, Math.Log10(adArgs[0]) / Math.Log10(0.5));

				} //end if
				else
					adData[i] = nUpper / 255.0;
			} //end for
			#endregion set Data Array

			for (int j = 0; j < nHeight; j++)
			{
				for (int i = 0; i < nWidth; i++)
				{
					if (adArgs[1] < 1.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adData[(int)(adImage[i, j, 1] * 255)];
						adOut[i, j, 2] = adData[(int)(adImage[i, j, 2] * 255)];
					}//end if
					else if (adArgs[1] < 2.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					} //end else
					else
					{//HSI
						adOut[i, j, 0] = adData[(int)(((adImage[i, j, 0] - min) / (max - min)) * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					}
				} //end for
			} //end for

			return adOut;
		}//end Exp

		public static double[, ,] Root(double[, ,] adImage, int nWidth, int nHeight, int nLower, int nUpper, out double[] adData, double[] adArgs)
		{
			double power = 1.0 / adArgs[0];
			double[, ,] adOut = new double[nWidth, nHeight, 3];
			double x;
			double min, max;
			min = MinMax(adImage, nWidth, nHeight, false);
			max = MinMax(adImage, nWidth, nHeight, true);
			//invalid parameter will result in default value

			#region set Data Array
			adData = new double[256];
			for (int i = 0; i < 256; i++)
			{
				if (i < nLower)
				{
					adData[i] = nLower / 255.0;
				} //end if
				else if (i <= nUpper)
				{
					x = i / 255.0;
					double t = (i - nLower) / (double)(nUpper - nLower);
					adData[i] = Math.Pow(t, power);

				} //end if
				else
					adData[i] = nUpper / 255.0;
			} //end for
			#endregion set Data Array

			for (int j = 0; j < nHeight; j++)
			{
				for (int i = 0; i < nWidth; i++)
				{
					if (adArgs[1] < 1.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adData[(int)(adImage[i, j, 1] * 255)];
						adOut[i, j, 2] = adData[(int)(adImage[i, j, 2] * 255)];
					}//end if
					else if (adArgs[1] < 2.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					} //end else
					else
					{//HSI
						adOut[i, j, 0] = adData[(int)(((adImage[i, j, 0] - min) / (max - min)) * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					}
				} //end for
			} //end for

			return adOut;
		}//end Root

		public static double[, ,] Contour(double[, ,] adImage, int nWidth, int nHeight, int nLower, int nUpper, out double[] adData, double[] adArgs)
		{
			int nPieces = (int)adArgs[0];
			if (nPieces <= 0) nPieces = 1;
			if (nPieces >= 16) nPieces = 16;
			int nPieceSize = 256 / nPieces;
			double[, ,] adOut = new double[nWidth, nHeight, 3];
			double x;
			double min, max;
			min = MinMax(adImage, nWidth, nHeight, false);
			max = MinMax(adImage, nWidth, nHeight, true);

			#region set Data Array
			adData = new double[256];
			for (int i = 0; i < 256; i++)
			{
				adData[i] = ((i * nPieces) % 256) / 255.0;
			} //end for
			#endregion set Data Array

			for (int j = 0; j < nHeight; j++)
			{
				for (int i = 0; i < nWidth; i++)
				{
					if (adArgs[1] < 1.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adData[(int)(adImage[i, j, 1] * 255)];
						adOut[i, j, 2] = adData[(int)(adImage[i, j, 2] * 255)];
					}//end if
					else if (adArgs[1] < 2.5)
					{
						adOut[i, j, 0] = adData[(int)(adImage[i, j, 0] * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					} //end else
					else
					{//HSI
						adOut[i, j, 0] = adData[(int)(((adImage[i, j, 0] - min) / (max - min)) * 255)];
						adOut[i, j, 1] = adImage[i, j, 1];
						adOut[i, j, 2] = adImage[i, j, 2];
					}
				} //end for
			} //end for

			return adOut;
		}//end Contour

		public static Bitmap Invert(Bitmap bmIn)
		{
			//set up for pointer manipulation
			#region Image pointer setup
			BitmapData srcBitData;
			Rectangle rc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			srcBitData = bmIn.LockBits(rc, ImageLockMode.ReadWrite, bmIn.PixelFormat);
			int stride = srcBitData.Stride;
			IntPtr srcPtr = srcBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			Console.WriteLine(imageBPP);
			#endregion Image pointer setup

			unsafe
			{
				byte* pixels = (byte*)(void*)srcPtr;
				for (int i = 0; i < bmIn.Height; i++)
				{
					int k = i * stride;
					for (int j = 0; j < stride; j++)
					{
						if (imageBPP == 32)
						{
							//skip alpha channel if it exists
							if (j % 4 != 3)
								pixels[k + j] = (byte)(255 - pixels[k + j]);
						}
						else
						{
							pixels[k + j] = (byte)(255 - pixels[k + j]);
						}
					} // end for
				} // end for 
				bmIn.UnlockBits(srcBitData);
			} // end unsafe
			return bmIn;
		}//end Invert

		public static void getBitSlice(Bitmap bmIn, ref Bitmap bmOut, byte mask)
		{
			if (bmOut == null)
				bmOut = new Bitmap(bmIn.Width, bmIn.Height, bmIn.PixelFormat);

			#region Image pointer setup
			BitmapData srcBitData;
			BitmapData destBitData;
			Rectangle rect = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			srcBitData = bmIn.LockBits(rect, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			destBitData = bmOut.LockBits(rect, ImageLockMode.WriteOnly, bmOut.PixelFormat);
			int stride = srcBitData.Stride;
			IntPtr srcPtr = srcBitData.Scan0;
			IntPtr destPtr = destBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			Console.WriteLine(imageBPP);
			#endregion Image pointer setup

			unsafe
			{
				byte* pixels = (byte*)(void*)srcPtr;
				byte* outpixels = (byte*)(void*)destPtr;
				int offset;

				for (int i = 0; i < bmIn.Height; i++)
				{
					offset = i * stride;
					for (int j = 0; j < stride; j++)
					{
						if (imageBPP >= 32 && j % 4 == 3)
							outpixels[offset + j] = 255;
						else
							outpixels[offset + j] = (byte)(pixels[offset + j] & (byte)mask);
					}
				}
			}
			bmIn.UnlockBits(srcBitData);
			bmOut.UnlockBits(destBitData);
			return;
		}

		private static double MinMax(double[,,] adImage, int nWidth, int nHeight, bool isMax) 
		{
			double val;

			if (isMax)
			{
				val = double.MinValue;
				for (int j = 0; j < nHeight; j++)
				{
					for (int i = 0; i < nWidth; i++)
					{
						if (adImage[i,j,0] > val)
						{
							val = adImage[i, j, 0];
						} //end if
					} //end for
				} //end for

			}//end if
			else
			{
				val = double.MaxValue;
				for (int j = 0; j < nHeight; j++)
				{
					for (int i = 0; i < nWidth; i++)
					{
						if (adImage[i, j, 0] < val)
						{
							val = adImage[i, j, 0];
						}//end if
					} //end for
				} //end for
			} //end else
			return val;
		}

	}

}
